package com.kaixinff.net;
import java.io.IOException;
import java.net.CookieHandler;
import java.net.HttpCookie;
import java.net.URI;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

public class DefaultCookieHandler extends CookieHandler {
	
//	private static Map<String, HttpCookie> cache = new HashMap<String, HttpCookie>();
	private Map<String, String> cache = new HashMap<String, String>();
	
	private static DefaultCookieHandler defaultCookieHandler;
	
	public static DefaultCookieHandler getInstance() {
		if (defaultCookieHandler == null) {
			defaultCookieHandler = new DefaultCookieHandler();
		}
		return defaultCookieHandler;
	}
	
	public DefaultCookieHandler() {
		
	}
	
	public DefaultCookieHandler(Collection<String> cookies) {
		setCookies(cookies);
	}
	
	void setCookies(Collection<String> cookies) {
		for (String cookie : cookies) {
			List<HttpCookie> httpCookies = HttpCookie.parse(cookie);
			if (httpCookies.size() > 0) {
				HttpCookie httpCookie = httpCookies.get(0);
				cache.put(httpCookie.getName(), cookie);
			}
		}
	}
	
	Collection<String> getCookies() {
		return cache.values();
	}
	
	/**
	 * Gets all the applicable cookies from a cookie cache for the specified uri
	 * in the request header.
	 * 
	 * HTTP protocol implementers should make sure that this method is called
	 * after all request headers related to choosing cookies are added, and
	 * before the request is sent.
	 * 
	 * @param uri
	 *            a <code>URI</code> to send cookies to in a request
	 * @param requestHeaders
	 *            - a Map from request header field names to lists of field
	 *            values representing the current request headers
	 * @return an immutable map from state management headers, with field names
	 *         "Cookie" or "Cookie2" to a list of cookies containing state
	 *         information
	 * 
	 * @throws IOException
	 *             if an I/O error occurs
	 * @throws IllegalArgumentException
	 *             if either argument is null
	 * @see #put(URI, Map)
	 */
	public Map<String, List<String>> get(URI uri,
			Map<String, List<String>> requestHeaders) throws IOException {
		StringBuilder sb = new StringBuilder();
		for (String item : cache.values()) {
			List<HttpCookie> cookies = HttpCookie.parse(item);
			if (cookies.size() > 0) {
				HttpCookie cookie = cookies.get(0);
				if (cookie.hasExpired()) {
					
				} else if (domainMatches(cookie.getDomain(), uri.getHost())){
					if (sb.length() > 0) {
						sb.append("; ");
					}
					sb.append(cookie.getName() + "=" + cookie.getValue());
				}
			}
		}
/*		for (HttpCookie cookie : cache.values()) {
			if (cookie.hasExpired()) {
//				cache.remove(cookie);
			} else if (domainMatches(cookie.getDomain(), uri.getHost())){
				if (sb.length() > 0) {
					sb.append("; ");
				}
				sb.append(cookie.getName() + "=" + cookie.getValue());
			}
		}*/
		Map<String, List<String>> cookieMap = new HashMap<String, List<String>>(
				requestHeaders);

		if (sb.length() > 0) {
			List<String> list = Collections.singletonList(sb.toString());
			cookieMap.put("Cookie", list);
		}
		return Collections.unmodifiableMap(cookieMap);

	}

	/**
	 * Sets all the applicable cookies, examples are response header fields that
	 * are named Set-Cookie2, present in the response headers into a cookie
	 * cache.
	 * 
	 * @param uri
	 *            a <code>URI</code> where the cookies come from
	 * @param responseHeaders
	 *            an immutable map from field names to lists of field values
	 *            representing the response header fields returned
	 * @throws IOException
	 *             if an I/O error occurs
	 * @throws IllegalArgumentException
	 *             if either argument is null
	 * @see #get(URI, Map)
	 */
	public void put(URI uri, Map<String, List<String>> responseHeaders)
			throws IOException {
		List<String> setCookieList = responseHeaders.get("Set-Cookie");
		if (setCookieList != null) {
			for (String item : setCookieList) {
				item = item.toLowerCase();
				item = item.replace("; httponly", "");
				if (item.indexOf("max-age=") == -1) {
					item += "; max-age=-1";
				}
				int beginIndex = item.indexOf("expires=");
				if (beginIndex != -1) {
					int endIndex = item.indexOf(";", beginIndex);
					if (endIndex == -1) {
						endIndex = item.length();
					}
					String dateStr = item.substring(beginIndex + 8, endIndex);
					SimpleDateFormat df = new SimpleDateFormat("EEE',' dd-MMM-yyyy HH:mm:ss 'gmt'", Locale.US);
			        df.setTimeZone(TimeZone.getTimeZone("GMT"));
			        try {
						Date date = df.parse(dateStr);
						SimpleDateFormat df2 = new SimpleDateFormat("EEE',' dd-MMM-yyyy HH:mm:ss 'GMT'");
						item = item.substring(0, beginIndex + 8) + df2.format(date) + item.substring(endIndex);
					} catch (ParseException e) {
						e.printStackTrace();
					}
		        } 
//				List<HttpCookie> cookies = HttpCookie.parse(item);
//				for (HttpCookie cookie : cookies) {
//					cache.put(cookie.getName(), cookie);
//				}
				
				List<HttpCookie> cookies = HttpCookie.parse(item);
				if (cookies.size() > 0) {
					HttpCookie cookie = cookies.get(0);
					cache.put(cookie.getName(), item);
				}
			}
		}
	}
	
	private boolean domainMatches(String domain, String host) {

		if (domain == null || host == null)
			return true;

		// if there's no embedded dot in domain and domain is not .local
		boolean isLocalDomain = ".local".equalsIgnoreCase(domain);
		int embeddedDotInDomain = domain.indexOf('.');
		if (embeddedDotInDomain == 0)
			embeddedDotInDomain = domain.indexOf('.', 1);
		if (!isLocalDomain
				&& (embeddedDotInDomain == -1 || embeddedDotInDomain == domain
						.length() - 1))
			return false;

		// if the host name contains no dot and the domain name is .local
		int firstDotInHost = host.indexOf('.');
		if (firstDotInHost == -1 && isLocalDomain)
			return true;

		int domainLength = domain.length();
		int lengthDiff = host.length() - domainLength;
		if (lengthDiff == 0) {
			// if the host name and the domain name are just string-compare
			// euqal
			return host.equalsIgnoreCase(domain);
		} else if (lengthDiff > 0) {
			// need to check H & D component
			String H = host.substring(0, lengthDiff);
			String D = host.substring(lengthDiff);

			return D.equalsIgnoreCase(domain);
		} else if (lengthDiff == -1) {
			// if domain is actually .host
			return (domain.charAt(0) == '.' && host.equalsIgnoreCase(domain
					.substring(1)));
		}

		return false;
	}
}
